/*
 * @(#)WebRendererHypertextUnitBuilder.java	0.1 06/05/07
 *
 * Copyright 2004 Fabio Gasparetti. All rights reserved.
 */

package org.tabularium.text.html;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

import java.io.*;
import java.net.MalformedURLException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.w3c.tidy.*;

import com.webrenderer.swing.*;
import com.webrenderer.swing.dom.*;
import com.webrenderer.swing.event.*;

import org.tabularium.net.UrlNormalizer;

/**
 * An implementation of <code>HypertextUnitBuilder</code> based on the
 * <tt>WebRenderer</tt> library.
 * <p>
 * This implementation does not tokenize text in Web pages or normalize links.
 * <p>
 * The <tt>JTidy</tt> library is used before parsing pages to clean up
 * malformed and faulty HTML.
 * 
 * @author Fabio Gasparetti
 * @version 0.1 06/05/07
 */

public class WebRendererHypertextUnitBuilder extends HypertextUnitBuilder {

	class PageLoadingMonitor {
		private int value;

		public PageLoadingMonitor(int initial) {
			value = initial;
		}

		synchronized public void start() {
			++value;
			notify();
		}

		synchronized public void stop() throws InterruptedException {
			while (value == 0)
				wait();
			--value;
		}
	}

	int loadingDocs = 0;

	public static ArrayList events = new ArrayList();

	protected StringBuffer textBuf = null;

	protected String baseHref = null;

	protected HashSet htmlDelimSet = new HashSet();

	protected HashSet htmlUnitDelimSet = new HashSet();

	protected HashSet htmlUnitDblDelimSet = new HashSet();

	protected HypertextUnitFactory unitFactory = null;

	protected IBrowserCanvas browser = null;

	protected HypertextUnit root = null;

	protected String url = null;

	protected boolean built = false;

	JPanel panel = null;

	JFrame frame = null;

	private File temp = new File("temp.html"); // file temporaneo dove mettere
												// la pagina ripulita

	private PageLoadingMonitor semaphore = new PageLoadingMonitor(0);

	public WebRendererHypertextUnitBuilder() {
		super();
	}

	public HypertextUnit build(String url, String htmltext,
			HypertextUnitFactory unitFactory) throws Exception {
		this.url = url;
		this.unitFactory = unitFactory;

		for (int i = 0; i < htmlDelim.length; i++) {
			this.htmlDelimSet.add(htmlDelim[i]);
		}
		for (int i = 0; i < htmlUnitDelim.length; i++) {
			this.htmlUnitDelimSet.add(htmlUnitDelim[i]);
		}
		for (int i = 0; i < htmlUnitDblDelim.length; i++) {
			this.htmlUnitDblDelimSet.add(htmlUnitDblDelim[i]);
		}

		// Licensing for swing edition
		BrowserFactory.setLicenseData("30dtrial", "802HC0S2R5201UQJ53JKK09S");

		System.out.println("new browser");
		browser = BrowserFactory.spawnMozilla();

		Component c = browser.getComponent();
		panel = new MyPanel(new BorderLayout());
		panel.add(c);
		frame = new MyFrame();
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setContentPane(panel);
		frame.setSize(750, 450);
		frame.setVisible(true);

		/* La pagina viene passata a Tidy, che restituisce la stessa ben formata */
		Tidy tidy = new Tidy();
		tidy.setXHTML(true);
		ByteArrayInputStream bis = new ByteArrayInputStream(htmltext.getBytes());
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		tidy.parse(bis, bos);
		bis.close();

		/*
		 * La pagina html ripulita da tidy viene scritta nella stringa tidyhtml.
		 * La stringa tidyhtml viene messa su un file temporaneo Il file
		 * temporaneo viene passato al browser
		 */
		String tidyhtml = bos.toString();
		FileOutputStream fos = new FileOutputStream(temp);
		PrintStream ps = new PrintStream(fos);

		// Listener basato su una funzione Javascript inserita nella pagina
		/*
		 * ps.print(addWRUI(tidyhtml)); browser.addBrowserListener(new
		 * BrowserAdapter(){ public void onBeforeNavigate(BrowserEvent e){ //
		 * confronta se il metodo che riceve da wrui  lo stesso che si aspetta
		 * if(e.getURL().equals("wrui://pageLoaded")){ pageLoaded(); } } });
		 */
		// Listener basato su onDocumentComplete
		ps.print(tidyhtml);
		browser.addNetworkListener(new MyNetworkListener(this, semaphore));
		browser.allowPopups(false);

		browser.loadURL(temp.getAbsolutePath());

		/*
		 * Da questo punto in poi esistono due thread. Bisognerebbe spostare il
		 * conteggio dei nodi nel thread appena creato, che  sicuramente
		 * sincronizzato con il caricamento della pagina
		 */

		int n = this.getDomNumAllChilds(new int[] { 0 });
		System.out.println("n before1: "+n);
		semaphore.stop(); // resta in attesa del semaforo verde

		n = this.getDomNumAllChilds(new int[] { 0 });
		System.out.println("n after1: "+n);


		try {
			traverseTree();
		} catch (Exception ex) {
			System.out.println(ex.toString());
			ex.printStackTrace();
		}

		n = this.getDomNumAllChilds(new int[] { 0 });
		System.out.println("n after2: "+n);

		WebRendererHypertextUnitBuilder.events.add("getdomnumallchilds " + n);


		frame.remove(panel);
		frame.dispose();
		temp.delete();
		return root;
	}

	/* Metodo che verr invocato da Javascript */
	public void pageLoaded() {
		System.out.println("--- Document Loaded ---");
		semaphore.start(); // mette il semaforo a verde
	}

	/*
	 * Aggiunge l'invocazione del protocollo WRUI come funzione sull'evento
	 * onLoad nel tag body di una pagina html
	 */
	private String addWRUI(String htmlpage) {
		StringBuffer sb = new StringBuffer(); // conterr la pagina con
												// l'evento inserito
		String event; // evento da aggiungere
		String regex; // espressione regolare per cercare il punto dove
						// inserire l'evento

		/*
		 * Espressione regolare che controlla che non vi sia gi un evento
		 * onload sul tag body
		 */
		String match = "<[ ]*body[ ]+.*onload[ ]*=[ ]*[\"'].*[\"'].*>";

		/* Compilazione dell'espressione regolare */
		Pattern p = Pattern.compile(match, Pattern.CASE_INSENSITIVE);
		Matcher m = p.matcher(htmlpage);

		boolean result = m.find(); // controlla se la stringa matcha
									// l'espressione regolare

		/*
		 * Se la stringa ha un matching con l'espressione, allora al body 
		 * associato un evento su onLoad, e bisogna aggiungere lo script in coda
		 * a quanto gi presente. Se la stringa non ha un matching invece si pu
		 * aggiungere direttamente l'evento nel tag body
		 */
		if (result) {
			event = "document.location=\"wrui://pageLoaded\"; ";
			regex = "<[ ]*body[ ]+.*onload[ ]*=[ ]*[\"']";

		} else {
			event = " onload='document.location=\"wrui://pageLoaded\"' ";
			regex = "<[ ]*body";
		}

		/* Compilazione dell'espressione regolare */
		Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
		Matcher matcher = pattern.matcher(htmlpage);

		/*
		 * Si fa restituire la prima istanza che soddisfa l'espressione
		 * regolare: in generale c' un solo tag body nella pagina. Qualora ve
		 * ne fosse pi di uno, si suppone che non siano allo stesso livello
		 * dell'albero, e lo script viene messo sul pi esterno dei due.
		 */
		result = matcher.find();
		String index = matcher.group();

		matcher.appendReplacement(sb, index + event);
		matcher.appendTail(sb);
		System.out.println(sb.toString());
		return sb.toString();
	}

	/*
	 * Cerca nella stringa il tag <body> e controlla che ad esso non vi sia un
	 * evento OnLoad associato. Se non  presente quest'ultimo associa un evento
	 * OnLoad a body
	 */
	private String addEventOnBody(String htmlpage, String kindofevent,
			String valueofevent) {
		StringBuffer sb = new StringBuffer();
		System.out.println("---- htmlpage ----");
		System.out.println(htmlpage);
		System.out.println("------------------");

		int indexofstartbody = htmlpage.indexOf("<body");
		sb.append(htmlpage.substring(0, indexofstartbody));
		String pagefrombody = htmlpage.substring(indexofstartbody);
		int indexofendbody = pagefrombody.indexOf(">");
		String bodytag = pagefrombody.substring(0, indexofendbody)
				.toLowerCase();

		/* Controlla che non vi sia gi lo stesso evento associato */
		if (bodytag.indexOf(kindofevent.toLowerCase()) != -1) {
			int indexofevent = bodytag.indexOf(kindofevent.toLowerCase());
			sb.append(bodytag.substring(0, indexofevent));

			String pagefromevent = bodytag.substring(indexofevent);
			int indexofqm1 = pagefromevent.indexOf("\"");
			sb.append(pagefromevent.substring(0, indexofqm1 + 1));

			String pagefromqm1 = pagefromevent.substring(indexofqm1 + 1);
			int indexofqm2 = pagefromqm1.indexOf("\"");
			sb.append(pagefromqm1.substring(0, indexofqm2));
			sb.append("; " + valueofevent);
			sb.append(pagefromqm1.substring(indexofqm2));
		} else {
			sb.append(bodytag.substring(0, bodytag.length()));
			sb.append(" " + kindofevent + "=\"" + valueofevent + "\"");
		}
		sb.append(pagefrombody.substring(indexofendbody));
		System.out.println("------sb-------");
		System.out.println(sb.toString());
		System.out.println("---------------");
		return sb.toString();
	}

	/*
	 * Inserisce una funzione javascript in un tag <script> subito dopo il tag
	 * <html>
	 */
	private String insertScript(String htmlpage, String name, String content) {
		StringBuffer sb = new StringBuffer();
		System.out.println("---- htmlpage ----");
		System.out.println(htmlpage);
		System.out.println("------------------");

		int indexofstarthtml = htmlpage.indexOf("<html");
		sb.append(htmlpage.substring(0, indexofstarthtml));
		String pagefromhtml = htmlpage.substring(indexofstarthtml);
		int indexofendhtml = pagefromhtml.indexOf(">");
		sb.append(pagefromhtml.substring(0, indexofendhtml + 1));
		sb.append("<SCRIPT JAVASCRIPT> function " + name + "() {" + content
				+ "}</SCRIPT>");
		sb.append(pagefromhtml.substring(indexofendhtml + 1));

		return sb.toString();
	}

	protected void traverseTree() {
		textBuf = new StringBuffer();
		baseHref = url;
		root = unitFactory.newHypertextUnit();
		root.type = "ROOT";
		root.parent = null;

		// IDocument document = browser.getDocument();

		System.out.println("N2:" + this.getDomNumAllChilds(new int[] { 0 }));
		traverseTree(new int[] { 0, 1 }, root);

		// if (elems.length() > 0)
		// traverseTree(elems.item(0), root);

		storeTextUnit(root);
		assignIds(root);

		// built = true;
	}

	protected String selectDomElement(int[] id) {
		StringBuffer sb = new StringBuffer();
		sb.append("document");
		// we ignore the first element because is the root
		for (int i = 1; i < id.length; i++) {
			sb.append(".childNodes[" + id[i] + "]");
		}
		return sb.toString();
	}

	protected void closeBrowser() {
		browser.executeScript("window.close()");
	}

	/**
	 * Normalize the DOM tree.
	 * 
	 * Although the normalization should be able to replace the multiple text
	 * nodes with a single text node containing all the text, this only works in
	 * Mozilla/Firefox/Netscape 6+. In Opera it puts all of the text into a
	 * single node and then truncates that node to 32 KB, so the contents of all
	 * except the first node are lost. Running the normalize method can crash
	 * Internet Explorer 6 and does not exist in Internet Explorer 5 on Windows.
	 * Therefore, it's better do normalization during parsing.
	 */
	protected String domNormalize() {
		return browser.executeScriptWithReturn("document.normalize()");
	}

	protected String getDomTagName(int[] id) {
		return browser.executeScriptWithReturn(selectDomElement(id)
				+ ".nodeName");
	}

	protected String getDomValue(int[] id) {
		return browser.executeScriptWithReturn(selectDomElement(id)
				+ ".nodeValue");
	}

	protected String getDomAttribute(int[] id, String attrName) {
		return browser.executeScriptWithReturn(selectDomElement(id)
				+ ".getAttribute('" + attrName + "')");
	}

	protected int getDomOffsetLeft(int[] id) {
		int offset = -1;
		String s = browser.executeScriptWithReturn(selectDomElement(id)
				+ ".offsetLeft");
		if (s != null) {
			try {
				offset = Integer.parseInt(s);
			} catch (NumberFormatException ex) {
			}
		}
		return offset;
	}

	protected int getDomOffsetTop(int[] id) {
		int offset = -1;
		String s = browser.executeScriptWithReturn(selectDomElement(id)
				+ ".offsetTop");
		if (s != null) {
			try {
				offset = Integer.parseInt(s);
			} catch (NumberFormatException ex) {
			}
		}
		return offset;
	}

	protected int getDomOffsetWidth(int[] id) {
		int offset = -1;
		String s = browser.executeScriptWithReturn(selectDomElement(id)
				+ ".offsetWidth");
		if (s != null) {
			try {
				offset = Integer.parseInt(s);
			} catch (NumberFormatException ex) {
			}
		}
		return offset;
	}

	protected int getDomOffsetHeight(int[] id) {
		int offset = -1;
		String s = browser.executeScriptWithReturn(selectDomElement(id)
				+ ".offsetHeight");
		if (s != null) {
			try {
				offset = Integer.parseInt(s);
			} catch (NumberFormatException ex) {
			}
		}
		return offset;
	}

	protected java.util.List getDomChilds(int[] id) {
		java.util.List childs = new ArrayList();
		int n = 0;
		String s = browser.executeScriptWithReturn(selectDomElement(id)
				+ ".childNodes.length");
		if (s != null) {
			try {
				n = Integer.parseInt(s);
			} catch (NumberFormatException ex) {
			}
		}
		for (int i = 0; i < n; i++) {
			int newid[] = new int[id.length + 1];
			System.arraycopy(id, 0, newid, 0, id.length);
			newid[id.length] = i;
			childs.add(newid);
		}
		return childs;
	}

	protected int getDomNumAllChilds(int[] id) {
		int n = 0;
		String s = browser.executeScriptWithReturn(selectDomElement(id)
				+ ".getElementsByTagName(\"*\").length");
		if (s != null) {
			try {
				n = Integer.parseInt(s);
			} catch (NumberFormatException ex) {
			}
		}
		return n;
	}

	protected void traverseTree(int[] id, HypertextUnit lastUnit) {
		String tagName = getDomTagName(id);
		// for (int i = 0; i < id.length; i++) {
		// System.out.print(id[i]+":");
		// }
		// System.out.println(tagName+":"+getDomValue(id));
		if (!"#text".equalsIgnoreCase(tagName)) {
			if ("base".equalsIgnoreCase(tagName)) {
				storeTextUnit(lastUnit);
				baseHref = getDomAttribute(id, "href");
			} else if ("a".equalsIgnoreCase(tagName)) {
				storeTextUnit(lastUnit);
				HypertextUnit unit = unitFactory.newHypertextUnit();
				unit.type = "LINK";
				unit.parent = lastUnit;
				unit.text = getDomAttribute(id, "title");
				String u = getDomAttribute(id, "href");
				if (u != null) {
					u = UrlNormalizer.unrelativizeUrl(baseHref, u);
					unit.url = u;
				}
				unit.offsetLeft = getDomOffsetLeft(id);
				unit.offsetTop = getDomOffsetTop(id);
				unit.width = getDomOffsetWidth(id);
				unit.height = getDomOffsetHeight(id);
				// push
				lastUnit.content.add(unit);
				lastUnit = unit;
			} else if ("img".equalsIgnoreCase(tagName)) {
				storeTextUnit(lastUnit);
				// adds the alt-attribute text of the images
				HypertextUnit unit = unitFactory.newHypertextUnit();
				unit.type = "IMG";
				unit.parent = lastUnit;
				unit.text = getDomAttribute(id, "alt");
				String u = getDomAttribute(id, "src");
				if (u != null) {
					u = UrlNormalizer.unrelativizeUrl(baseHref, u);
					unit.url = u;
				}
				unit.offsetLeft = getDomOffsetLeft(id);
				unit.offsetTop = getDomOffsetTop(id);
				unit.width = getDomOffsetWidth(id);
				unit.height = getDomOffsetHeight(id);
				lastUnit.content.add(unit);
			} else if ("title".equalsIgnoreCase(tagName)) {
				HypertextUnit unit = unitFactory.newHypertextUnit();
				unit.type = "TITLE";
				// unit.text = node.getNodeValue();
				unit.parent = lastUnit;
				unit.offsetLeft = getDomOffsetLeft(id);
				unit.offsetTop = getDomOffsetTop(id);
				unit.width = getDomOffsetWidth(id);
				unit.height = getDomOffsetHeight(id);
				lastUnit.content.add(unit);
			}
			if (htmlDelimSet.contains(tagName.toUpperCase())) {
				storeTextUnit(lastUnit);
				HypertextUnit unit = unitFactory.newHypertextUnit();
				unit.type = tagName.toUpperCase();
				unit.parent = lastUnit;
				unit.offsetLeft = getDomOffsetLeft(id);
				unit.offsetTop = getDomOffsetTop(id);
				unit.width = getDomOffsetWidth(id);
				unit.height = getDomOffsetHeight(id);
				lastUnit.content.add(unit);
			} else if (htmlUnitDelimSet.contains(tagName.toUpperCase())) {
				storeTextUnit(lastUnit);
				// add the delim
				HypertextUnit unit = unitFactory.newHypertextUnit();
				unit.type = tagName.toUpperCase();
				unit.parent = lastUnit;
				unit.offsetLeft = getDomOffsetLeft(id);
				unit.offsetTop = getDomOffsetTop(id);
				unit.width = getDomOffsetWidth(id);
				unit.height = getDomOffsetHeight(id);
				lastUnit.content.add(unit);
				lastUnit = unit;
			} else if (htmlUnitDblDelimSet.contains(tagName.toUpperCase())) {
				storeTextUnit(lastUnit);
				// create a new unit
				HypertextUnit unit = unitFactory.newHypertextUnit();
				unit.type = tagName.toUpperCase();
				unit.parent = lastUnit;
				unit.offsetLeft = getDomOffsetLeft(id);
				unit.offsetTop = getDomOffsetTop(id);
				unit.width = getDomOffsetWidth(id);
				unit.height = getDomOffsetHeight(id);
				lastUnit.content.add(unit);
				lastUnit = unit;
			}
		} else { // text node
			textBuf.append(getDomValue(id) + " ");
		}
		java.util.List childs = getDomChilds(id);
		Iterator iter = childs.iterator();
		while (iter.hasNext()) {
			int[] childid = (int[]) iter.next();
			traverseTree(childid, lastUnit);
		}
		// before going up one level store text
		storeTextUnit(lastUnit);
		/*
		 * java.util.List childs = getDomChilds(id); Iterator iter =
		 * childs.iterator(); while (iter.hasNext()) { int[] childid = (int[])
		 * iter.next(); System.out.print("TAG: " + this.getDomTagName(childid));
		 * System.out.print("\tVALUE: " + this.getDomValue(childid));
		 * System.out.print("\tATTR HREF: " + this.getDomAttribute(childid,
		 * "HREF")); System.out.print("\tATTR TITLE: " +
		 * this.getDomAttribute(childid, "title")); System.out.print("\tATTR
		 * ALT: " + this.getDomAttribute(childid, "alt"));
		 * System.out.print("\tATTR SRC: " + this.getDomAttribute(childid,
		 * "src")); System.out.print("\tOFFH: " +
		 * this.getDomOffsetHeight(childid)); System.out.print("\tOFFW: " +
		 * this.getDomOffsetWidth(childid)); System.out.print("\tOFFT: " +
		 * this.getDomOffsetTop(childid)); System.out.println("\tOFFL: " +
		 * this.getDomOffsetLeft(childid)); traverseTree(childid); }
		 */
	}

	protected void traverseTree(IElement node, HypertextUnit lastUnit) {
		String tagName = node.getTagName();
		// if (node.getTagName().eq == org.w3c.dom.Node.ELEMENT_NODE) {
		if ("base".equalsIgnoreCase(tagName)) {
			storeTextUnit(lastUnit);
			baseHref = node.getAttribute("href", 0);
		} else if ("a".equalsIgnoreCase(tagName)) {
			storeTextUnit(lastUnit);
			HypertextUnit unit = unitFactory.newHypertextUnit();
			unit.type = "LINK";
			unit.parent = lastUnit;
			unit.text = node.getAttribute("title", 0);
			String u = node.getAttribute("href", 0);
			if (u != null) {
				u = UrlNormalizer.unrelativizeUrl(baseHref, u);
				unit.url = u;
			}
			unit.offsetLeft = (int) node.getOffsetLeft();
			unit.offsetTop = (int) node.getOffsetTop();
			unit.width = (int) node.getOffsetWidth();
			unit.height = (int) node.getOffsetHeight();
			// push
			lastUnit.content.add(unit);
			lastUnit = unit;
		} else if ("img".equalsIgnoreCase(tagName)) {
			storeTextUnit(lastUnit);
			// adds the alt-attribute text of the images
			HypertextUnit unit = unitFactory.newHypertextUnit();
			unit.type = "IMG";
			unit.parent = lastUnit;
			unit.text = node.getAttribute("alt", 0);
			String u = node.getAttribute("src", 0);
			if (u != null) {
				u = UrlNormalizer.unrelativizeUrl(baseHref, u);
				unit.url = u;
			}
			unit.offsetLeft = (int) node.getOffsetLeft();
			unit.offsetTop = (int) node.getOffsetTop();
			unit.width = (int) node.getOffsetWidth();
			unit.height = (int) node.getOffsetHeight();
			lastUnit.content.add(unit);
		} else if ("title".equalsIgnoreCase(tagName)) {
			HypertextUnit unit = unitFactory.newHypertextUnit();
			unit.type = "TITLE";
			// unit.text = node.getNodeValue();
			unit.parent = lastUnit;
			unit.offsetLeft = (int) node.getOffsetLeft();
			unit.offsetTop = (int) node.getOffsetTop();
			unit.width = (int) node.getOffsetWidth();
			unit.height = (int) node.getOffsetHeight();
			lastUnit.content.add(unit);
		}
		if (htmlDelimSet.contains(tagName.toUpperCase())) {
			storeTextUnit(lastUnit);
			HypertextUnit unit = unitFactory.newHypertextUnit();
			unit.type = tagName.toUpperCase();
			unit.parent = lastUnit;
			unit.offsetLeft = (int) node.getOffsetLeft();
			unit.offsetTop = (int) node.getOffsetTop();
			unit.width = (int) node.getOffsetWidth();
			unit.height = (int) node.getOffsetHeight();
			lastUnit.content.add(unit);
		} else if (htmlUnitDelimSet.contains(tagName.toUpperCase())) {
			storeTextUnit(lastUnit);
			// add the delim
			HypertextUnit unit = unitFactory.newHypertextUnit();
			unit.type = tagName.toUpperCase();
			unit.parent = lastUnit;
			unit.offsetLeft = (int) node.getOffsetLeft();
			unit.offsetTop = (int) node.getOffsetTop();
			unit.width = (int) node.getOffsetWidth();
			unit.height = (int) node.getOffsetHeight();
			lastUnit.content.add(unit);
			lastUnit = unit;
		} else if (htmlUnitDblDelimSet.contains(tagName.toUpperCase())) {
			storeTextUnit(lastUnit);
			// create a new unit
			HypertextUnit unit = unitFactory.newHypertextUnit();
			unit.type = tagName.toUpperCase();
			unit.parent = lastUnit;
			unit.offsetLeft = (int) node.getOffsetLeft();
			unit.offsetTop = (int) node.getOffsetTop();
			unit.width = (int) node.getOffsetWidth();
			unit.height = (int) node.getOffsetHeight();
			lastUnit.content.add(unit);
			lastUnit = unit;
		}
		// } else if (node.getNodeType() == org.w3c.dom.Node.TEXT_NODE) {
		// textBuf.append(node.getNodeValue() + " ");
		// }
		IElementCollection childs = node.getChildElements();
		for (int i = 0; i < childs.length(); i++) {
			traverseTree(childs.item(i), lastUnit);
		}
		// before going up one level store text
		storeTextUnit(lastUnit);
	}

	// collect last text and assign it to the last unit
	protected void storeTextUnit(HypertextUnit unit) {
		// collect last text and assign it to the last unit
		String text = textBuf.toString().trim();
		if (text.length() > 0) {
			HypertextUnit u = unitFactory.newHypertextUnit();
			u.text = text;
			u.type = "TEXT";
			u.parent = unit;
			// if we already inserted a text node, update it
			if (unit.content.size() > 0) {
				HypertextUnit past = (HypertextUnit) unit.content
						.get(unit.content.size() - 1);
				if (past.type.equalsIgnoreCase("TEXT")) {
					past.text = past.text.concat(" " + text);
				}
			} else {
				unit.content.add(u);
			}
			textBuf = new StringBuffer();
		}
	}

	public static void main(String[] args) {
		try {
			/* FIXME funzione main */
			InputStream is = new FileInputStream("cnn-hp.html");

			BufferedReader reader = new BufferedReader(
					new InputStreamReader(is));

			String line;
			StringBuffer doc = new StringBuffer();
			while ((line = reader.readLine()) != null) {
				doc.append(line + "\n");
			}
			is.close();
			WebRendererHypertextUnitBuilder hu = new WebRendererHypertextUnitBuilder();
			HypertextUnit root = hu.build("http://www.cnn.com/",
					doc.toString(), new DefaultHypertextUnitFactory());
			if (root != null)
				System.out.println("root:" + root.toIndentString());
		} catch (Exception ex) {
			System.out.println(ex.toString());
			ex.printStackTrace();
		}
	}
}

/* -------------------------------------------------------------------------------------- */
class MyBrowserListener implements BrowserListener {
	WebRendererHypertextUnitBuilder builder;

	public void onFavicon(BrowserEvent e) {
		System.out.println("onFavicon");
	}

	public MyBrowserListener(WebRendererHypertextUnitBuilder b) {
		builder = b;
	}

	public void onBeforeNavigate(BrowserEvent e) {
		System.out.println("onBeforeNavigate");
	}

	public void onContextMenu(BrowserEvent e) {
		System.out.println("onContextMenu");
	}

	public void onLinkChange(BrowserEvent e) {
		System.out.println("onLinkChange");
	}

	public void onLoadIntercept(BrowserEvent e) {
		System.out.println(e.toString());
		WebRendererHypertextUnitBuilder.events.add(e.toString());
		/*
		 * IBrowserCanvas canvas = (IBrowserCanvas) e.getSource();
		 * 
		 * Canvas c = (Canvas)canvas; if (c.isValid() == true) builder.built =
		 * true;
		 */
	}

	public void onNavigationCancelled(BrowserEvent e) {
		System.out.println("onNavigationCancelled");
	}

	public void onTitleChange(BrowserEvent e) {
		System.out.println("onTitleChange");
	}

	public void onURLChange(BrowserEvent e) {
		System.out.println("onURLChange");
	}

}

class MyNetworkListener implements
		NetworkListener {
	WebRendererHypertextUnitBuilder builder;

	WebRendererHypertextUnitBuilder.PageLoadingMonitor semaphore;

	public MyNetworkListener(WebRendererHypertextUnitBuilder b, WebRendererHypertextUnitBuilder.PageLoadingMonitor semaphore) {		
		this.builder = b;
		this.semaphore = semaphore;
	}

	public void onDocumentComplete(com.webrenderer.swing.event.NetworkEvent e) {
		System.out.println("--- Document Loaded ---");
		semaphore.start();
	}

	public void onHTTPInterceptHeaders(
			com.webrenderer.swing.event.NetworkEvent e) {
		builder.loadingDocs++;
		// System.out.println("onHTTPInterceptHeaders ("+builder.loadingDocs+")
		// :"+e.getURL());
		// If the loading URL contains "www.google.com"
		if (e.getURL().indexOf("www.google.com") != -1) {
			// Stopping the document from loading
			// e.blockLoad();
		}
	}

	public void onDocumentLoad(com.webrenderer.swing.event.NetworkEvent e) {
		// System.out.println("onDocumentLoad ("+builder.loadingDocs+") :" +
		// e.getURL());
	}

	public void onHTTPResponse(com.webrenderer.swing.event.NetworkEvent e) {
		builder.loadingDocs--;
		// System.out.println("onHTTPResponse - (e.getStatusText()): "+
		// e.getStatusText());
		// System.out.println("onHTTPResponse - (e.getStatus()): "+
		// e.getStatus());
		// System.out.println("onHTTPResponse - (e.getHeaders()): "+
		// e.getHeaders());
		// System.out.println("onHTTPResponse ("+builder.loadingDocs+") :
		// "+e.getURL());
	}

	public void onNetworkError(com.webrenderer.swing.event.NetworkEvent e) {
		// System.out.println("onNetworkError - (e.getFailure()): "+
		// e.getFailure());;
	}

	public void onNetworkStatus(com.webrenderer.swing.event.NetworkEvent e) {
		// System.out.println("onNetworkStatus - (e.getStatus()):
		// "+e.getStatus());;
	}

	public void onProgressChange(com.webrenderer.swing.event.NetworkEvent e) {
		// System.out.println("onProgressChange - (e.getCurrentProgress): "+
		// e.getCurrentProgress());
		// System.out.println("onProgressChange - (e.getMaximumProgress): "+
		// e.getMaximumProgress());;
	}
}

class MyWindowListener extends com.webrenderer.swing.event.WindowAdapter {
	WebRendererHypertextUnitBuilder builder;

	public MyWindowListener(WebRendererHypertextUnitBuilder b) {
		builder = b;
	}

	// Fired when a popup window is to be displayed
	public void onNewWindow(com.webrenderer.swing.event.WindowEvent e) {
		// If this event is captured you must handle the display of the
		// popup window
		System.out.println("onNewWindow");
		IBrowserCanvas browserPopup = e.getPopupBrowser();
		WebRendererHypertextUnitBuilder.events.add("popup:" + e.getURL());
		System.out.println("popup:" + e.getURL());
		// IBrowserCanvas browser = e.getPopupBrowser();
		// The browser can then be placed into a JFrame and displayed.
		// This is useful for customizing popups.
	}

	// Fired when the Window receives a resize event
	public void onWindowResize(com.webrenderer.swing.event.WindowEvent e) {
		System.out.println("Browser resize event: - Width: " + e.getWidth()
				+ " and Height: " + e.getHeight());
	}

	// Fired when the browser window is destroyed
	public void onWindowDestroy(com.webrenderer.swing.event.WindowEvent e) {
		System.out.println("Browser Window destroyed");
	}
}

/*
 * class MyDOMListener implements DOMListener { WebRendererHypertextUnitBuilder
 * builder;
 * 
 * public MyDOMListener(WebRendererHypertextUnitBuilder b) { builder = b; }
 * 
 * public void onButtonClicked(DOMEvent e) { System.out.println(e.toString()); }
 * public void onCheckBoxChecked(DOMEvent e) { System.out.println(e.toString()); }
 * public void onCheckBoxUnchecked(DOMEvent e) {
 * System.out.println(e.toString()); } public void onDropdownChanged(DOMEvent e) {
 * System.out.println(e.toString()); } public void onInputTextChanged(DOMEvent
 * e) { System.out.println(e.toString()); } public void onRadioSelected(DOMEvent
 * e) { System.out.println(e.toString()); } }
 */

class MyComponentListener implements ComponentListener {
	WebRendererHypertextUnitBuilder builder;

	public MyComponentListener(WebRendererHypertextUnitBuilder b) {
		builder = b;
	}

	public void componentHidden(ComponentEvent e) {
		System.out.println("componentHidden");
	}

	public void componentMoved(ComponentEvent e) {
		System.out.println("componentMoved");
	}

	public void componentResized(ComponentEvent e) {
		System.out.println("componentResized");
	}

	public void componentShown(ComponentEvent e) {
		System.out.println("componentShown");
	}
}

class MyPanel extends JPanel {
	public MyPanel() {
		super();
	}

	public MyPanel(boolean isDoubleBuffered) {
		super(isDoubleBuffered);
	}

	public MyPanel(LayoutManager layout) {
		super(layout);
	}

	public MyPanel(LayoutManager layout, boolean isDoubleBuffered) {
		super(layout, isDoubleBuffered);
	}

	public void processEvent(AWTEvent evt) {
		super.processEvent(evt);
		WebRendererHypertextUnitBuilder.events.add("panel:" + evt.toString());
	}
}

class MyFrame extends JFrame {
	public MyFrame() {
		super();
	}

	public MyFrame(GraphicsConfiguration gc) {
		super(gc);
	}

	public MyFrame(String title) {
		super(title);
	}

	public MyFrame(String title, GraphicsConfiguration gc) {
		super(title, gc);
	}

	public void processEvent(AWTEvent evt) {
		// System.out.println("frame:"+evt.toString());
		super.processEvent(evt);
		WebRendererHypertextUnitBuilder.events.add("frame:" + evt.toString());
	}
}

class OnLoadAction implements
		com.webrenderer.swing.dom.w3c.events.EventListener {
	public void handleEvent(com.webrenderer.swing.dom.w3c.events.Event evt) {
		// Perform some actions here...

		// ...for example start an animation loop:
		WebRendererHypertextUnitBuilder.events.add("onload from OnLoadAction: "
				+ evt.toString());
		System.out.println("onload from OnLoadAction");
	}
}

class OnMutationAction implements
		com.webrenderer.swing.dom.w3c.events.EventListener {
	public void handleEvent(com.webrenderer.swing.dom.w3c.events.Event evt) {
		// Perform some actions here...

		// ...for example start an animation loop:
		WebRendererHypertextUnitBuilder.events
				.add("onmutation from OnMutationAction: " + evt.toString());
		System.out.println("onmutation from OnMutationAction");
	}
}